package server;

import client.MapleClient;
import client.inventory.IItem;
import client.inventory.ItemLoader;
import client.inventory.MapleInventoryType;
import constants.GameConstants;
import database.DatabaseConnection;
import database.DatabaseException;
import tools.MaplePacketCreator;
import tools.Pair;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

public class MapleStorage implements Serializable {

  private static final long serialVersionUID = 9179541993413738569L;
  private int id;
  private int accountId;
  private List<IItem> items;
  private int meso;
  private byte slots;
  private boolean changed = false;
  private Map<MapleInventoryType, List<IItem>> typeItems = new EnumMap<MapleInventoryType, List<IItem>>(MapleInventoryType.class);

  private MapleStorage(int id, byte slots, int meso, int accountId) {
    this.id = id;
    this.slots = slots;
    this.items = new LinkedList<IItem>();
    this.meso = meso;
    this.accountId = accountId;
  }

  public static int create(int id) throws SQLException {
    Connection con = DatabaseConnection.getConnection();
    PreparedStatement ps = con.prepareStatement("INSERT INTO storages (accountid, slots, meso) VALUES (?, ?, ?)", DatabaseConnection.RETURN_GENERATED_KEYS);
    ps.setInt(1, id);
    ps.setInt(2, 4);
    ps.setInt(3, 0);
    ps.executeUpdate();

    int storageid;
    ResultSet rs = ps.getGeneratedKeys();
    if (rs.next()) {
      storageid = rs.getInt(1);
      ps.close();
      rs.close();
      return storageid;
    }
    ps.close();
    rs.close();
    throw new DatabaseException("Inserting char failed.");
  }

  public static MapleStorage loadStorage(int id) {
    MapleStorage ret = null;
    int storeId;
    try {
      Connection con = DatabaseConnection.getConnection();
      PreparedStatement ps = con.prepareStatement("SELECT * FROM storages WHERE accountid = ?");
      ps.setInt(1, id);
      ResultSet rs = ps.executeQuery();

      if (rs.next()) {
        storeId = rs.getInt("storageid");
        ret = new MapleStorage(storeId, rs.getByte("slots"), rs.getInt("meso"), id);
        rs.close();
        ps.close();

        for (Pair<IItem, MapleInventoryType> mit : ItemLoader.STORAGE.loadItems(false, id).values()) {
          ret.items.add(mit.getLeft());
        }
      } else {
        storeId = create(id);
        ret = new MapleStorage(storeId, (byte) 4, 0, id);
        rs.close();
        ps.close();
      }
    } catch (SQLException ex) {
      System.err.println("Error loading storage");
      ex.printStackTrace();
    }
    return ret;
  }

  public void saveToDB() {
    if (!changed) {
      return;
    }
    try {
      Connection con = DatabaseConnection.getConnection();

      PreparedStatement ps = con.prepareStatement("UPDATE storages SET slots = ?, meso = ? WHERE storageid = ?");
      ps.setInt(1, slots);
      ps.setInt(2, meso);
      ps.setInt(3, id);
      ps.executeUpdate();
      ps.close();

      List<Pair<IItem, MapleInventoryType>> listing = new ArrayList<Pair<IItem, MapleInventoryType>>();
      for (final IItem item : items) {
        listing.add(new Pair<IItem, MapleInventoryType>(item, GameConstants.getInventoryType(item.getItemId())));
      }
      ItemLoader.STORAGE.saveItems(listing, accountId);
    } catch (SQLException ex) {
      System.err.println("Error saving storage" + ex);
    }
  }

  public IItem takeOut(byte slot) {
    if (slot >= items.size() || slot < 0) {
      return null;
    }
    changed = true;
    IItem ret = items.remove(slot);
    MapleInventoryType type = GameConstants.getInventoryType(ret.getItemId());
    typeItems.put(type, new ArrayList<IItem>(filterItems(type)));
    return ret;
  }

  public void store(IItem item) {
    changed = true;
    items.add(item);
    MapleInventoryType type = GameConstants.getInventoryType(item.getItemId());
    typeItems.put(type, new ArrayList<IItem>(filterItems(type)));
  }

  public List<IItem> getItems() {
    return Collections.unmodifiableList(items);
  }

  private List<IItem> filterItems(MapleInventoryType type) {
    List<IItem> ret = new LinkedList<IItem>();

    for (IItem item : items) {
      if (GameConstants.getInventoryType(item.getItemId()) == type) {
        ret.add(item);
      }
    }
    return ret;
  }

  public byte getSlot(MapleInventoryType type, byte slot) {
    // MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
    byte ret = 0;
    final List<IItem> it = typeItems.get(type);
    if (slot >= it.size() || slot < 0) {
      return -1;
    }
    for (IItem item : items) {
      if (item == it.get(slot)) {
        return ret;
      }
      ret++;
    }
    return -1;
  }

  public void sendStorage(MapleClient c, int npcId) {
    // sort by inventorytype to avoid confusion
    Collections.sort(items, new Comparator<IItem>() {

      public int compare(IItem o1, IItem o2) {
        if (GameConstants.getInventoryType(o1.getItemId()).getType() < GameConstants.getInventoryType(o2.getItemId()).getType()) {
          return -1;
        } else if (GameConstants.getInventoryType(o1.getItemId()) == GameConstants.getInventoryType(o2.getItemId())) {
          return 0;
        } else {
          return 1;
        }
      }
    });
    for (MapleInventoryType type : MapleInventoryType.values()) {
      typeItems.put(type, new ArrayList<IItem>(items));
    }
    c.getSession().write(MaplePacketCreator.getStorage(npcId, slots, items, meso));
  }

  public void sendStored(MapleClient c, MapleInventoryType type) {
    c.getSession().write(MaplePacketCreator.storeStorage(slots, type, typeItems.get(type)));
  }

  public void sendTakenOut(MapleClient c, MapleInventoryType type) {
    c.getSession().write(MaplePacketCreator.takeOutStorage(slots, type, typeItems.get(type)));
  }

  public int getMeso() {
    return meso;
  }

  public IItem findById(int itemId) {
    for (IItem item : items) {
      if (item.getItemId() == itemId) {
        return item;
      }
    }
    return null;
  }

  public void setMeso(int meso) {
    if (meso < 0) {
      return;
    }
    changed = true;
    this.meso = meso;
  }

  public void sendMeso(MapleClient c) {
    c.getSession().write(MaplePacketCreator.mesoStorage(slots, meso));
  }

  public boolean isFull() {
    return items.size() >= slots;
  }

  public int getSlots() {
    return slots;
  }

  public void increaseSlots(byte gain) {
    changed = true;
    this.slots += gain;
  }

  public void setSlots(byte set) {
    changed = true;
    this.slots = set;
  }

  public void close() {
    typeItems.clear();
  }
}
